package com.share.example.stock.service.impl;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.share.common.common.Page;
import com.share.common.utils.DateUtil;
import com.share.common.utils.DecimalUtil;
import com.share.common.utils.ObjUtil;
import com.share.common.utils.ThreadUtil;
import com.share.example.stock.enums.StockStatusEnum;
import com.share.example.stock.mapper.StockBaseInfoMapper;
import com.share.example.stock.mapper.StockDayPriceMapper;
import com.share.example.stock.mapper.TradeDateMapper;
import com.share.example.stock.model.ak.OpenCloseLowHigh;
import com.share.example.stock.model.ak.ZhAHist;
import com.share.example.stock.model.ak.ZhASpotEm;
import com.share.example.stock.model.db.StockBaseInfo;
import com.share.example.stock.model.db.StockDayPrice;
import com.share.example.stock.model.db.TradeDate;
import com.share.example.stock.model.rsp.StockDayPricePageReq;
import com.share.example.stock.model.vo.Code4Pirce;
import com.share.example.stock.model.vo.DayCodePrice;
import com.share.example.stock.model.vo.DayPriceVo;
import com.share.example.stock.service.*;
import com.share.example.stock.utils.CalcUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

/**
 * <p>
 * 目标地址: http://vip.stock.finance.sina.com.cn/mkt/#hs_s
 * 描述: 中国股票指数数据, 注意该股票指数指新浪提供的国内股票指数
 * 限量: 单次返回所有指数的实时行情数据  服务实现类
 * </p>
 *
 * @author author
 * @since 2023-06-10
 */
@Slf4j
@Service
public class StockDayPriceServiceImpl extends ServiceImpl<StockDayPriceMapper, StockDayPrice> implements IStockDayPriceService {
    //连续清理记错计数器
    private AtomicInteger errorCount = new AtomicInteger(0);

    @Resource
    private StockDayPriceMapper stockDayPriceMapper;
    @Resource
    private StockBaseInfoMapper stockBaseInfoMapper;
    @Autowired
    private AkService akService;

    @Autowired
    private IStockBaseInfoService iStockBaseInfoService;

    @Resource
    private TradeDateMapper tradeDateMapper;

    @Autowired
    private ITradeDateService iTradeDateService;

    @Autowired
    private StrategyService strategyService;

    //重置错误id
    @Override
    public void resetErrorCount(int init) {
        errorCount.set(init);
    }

    @Override
    public List<StockDayPrice> downToDay() {
        Date date = new Date();
        Boolean isExistNewCode = false;//默认不存在
        String strDate = DateUtil.getDate(date);
        List<StockDayPrice> insertList = new LinkedList<>();
        List<Code4Pirce> monitorList = new LinkedList<>();
        List<ZhASpotEm> zhIndexSpots = akService.stock_zh_a_spot_em();
        List<String> codeList = stockDayPriceMapper.listCodeByDay(strDate);

        Map<String, DayCodePrice> yesterdayPriceMap = new HashMap<>();
        if (CollectionUtils.isEmpty(codeList)) {
            //当天的第一次
            yesterdayPriceMap = stockDayPriceMapper.mapLastRecordNotContainEnd(strDate);
        }

        for (ZhASpotEm item : zhIndexSpots) {
            //1.基表数据更新
            StockBaseInfo baseInfo = new StockBaseInfo();
            baseInfo.setLastPrice(item.getLastPrice());
            baseInfo.setTotalCapital(item.getTotalCapital());
            baseInfo.setFloatingCapital(item.getFloatingCapital());
            baseInfo.setPeD(item.getPriceToBookRatioDynamic());
            baseInfo.setTurnoverRate(item.getTurnoverRate());
            baseInfo.setLastPriceRate(item.getRiseOrFallRate());//比例
            baseInfo.setLastVolume(item.getVolume());//
            baseInfo.setLastPriceUpdateTime(date);//拉取的时间
            baseInfo.setCode(item.getCode());
            if (item.getHigh() == null && item.getLow() == null && item.getVolume() == null) {
                // 发现当天无数据,不处理直接跳过
                // baseInfo.setStatus(StockStatusEnum.OTHER.getCode()); //僵尸状态
            }

            //2.简单的除权检测，新股的话,就直接重新拉取即可
            DayCodePrice yesterdayPrice = yesterdayPriceMap.get(item.getCode());
            if (yesterdayPrice != null && yesterdayPrice.getPrice() != null && item.getOpen() != null) {
                Double rate = DecimalUtil.growthRatio(yesterdayPrice.getPrice(), item.getOpen(), 4);
                if (rate != null && Math.abs(rate) > 0.22) {
                    log.info("发生了除权code={}", item.getCode());
                    baseInfo.setDayPullCount(0);//设置为待拉取
                }
            }

            //属性简单的检测
            Map<String, Object> tmpBaseInfo = ObjUtil.toMapIgnoreNull(baseInfo);
            if (tmpBaseInfo != null && tmpBaseInfo.size() > 2) {
                ThreadUtil.execute(() -> stockBaseInfoMapper.updateByCode(baseInfo)); //异步执行
            } else {
                log.error("异常数据item={}", JSON.toJSON(item));
            }

            //4.检查是否时新股
            StockBaseInfo stockInfo = iStockBaseInfoService.getStockCodeMap(item.getCode());
            if (stockInfo == null) {
                ThreadUtil.execute(() -> iStockBaseInfoService.isNewCode(item.getCode())); //异步执行
                isExistNewCode = true;
            }

            //3.日表数据处理
            StockDayPrice record = new StockDayPrice();
            record.setDate(strDate);
            record.setHigh(item.getHigh());
            record.setLow(item.getLow());
            record.setCode(item.getCode());
            record.setName(item.getName());
            record.setOpen(item.getOpen());
            record.setChangePrice(item.getRiseOrFallPrice());
            record.setPriceRate(item.getRiseOrFallRate());
            record.setYclose(item.getYclose());
            record.setClose(item.getLastPrice());
            record.setVolume(item.getVolume());
            record.setAmount(item.getAmount());

            //价格均值计算
            OpenCloseLowHigh cp = CalcUtil.calcPrice(item.getOpen(), item.getLastPrice(), item.getLow(), item.getHigh());
            record.setHlO(cp.getHighSlowDopen());
            record.setCoO(cp.getCloseSopenDopen());
            record.setHl(cp.getHighSlow());
            record.setCo(cp.getCloseSopen());
            record.setClHl(cp.getCloseSlowDhighSlow());
            record.setOlHl(cp.getOpenSlowDhighSlow());
            record.setHcHl(cp.getHighScloseDhighSlow());
            record.setHoHl(cp.getHighSopenDhighSlow());
            record.setAvgHl(cp.getAvghighSlow());
            record.setAvgOc(cp.getAvgOpenclose());

            //日数据采用更新的方式处理
            if (codeList.contains(item.getCode())) {
                ThreadUtil.execute(() -> stockDayPriceMapper.updateByCodeAndDate(record));//异步执行
            } else {
                //属性简单的检测 ---添加到记录
                Map<String, Object> tmpZhASpotEm = ObjUtil.toMapIgnoreNull(record);
                if (tmpZhASpotEm != null && tmpZhASpotEm.size() > 1) {
                    insertList.add(record);
                }

                //数据入库
                if (insertList.size() > 400) {
                    log.info("批量插入,size={}", insertList.size());
                    int counti = stockDayPriceMapper.insertbatch(insertList);
                    insertList.clear();
                }
            }

            //监控数据收集
            Code4Pirce code4Pirce = new Code4Pirce(item.getCode(), item.getOpen(), item.getLastPrice(), item.getLow(), item.getHigh());
            monitorList.add(code4Pirce);
        }

        //数据跟踪---异步处理-----数据回归
        Date curDate = new Date();
        ThreadUtil.execute(() -> strategyService.strategyMonitor(curDate, monitorList));

        //数据入库
        if (insertList.size() > 0) {
            log.info("批量插入,size={}", insertList.size());
            int counti = stockDayPriceMapper.insertbatch(insertList);
            insertList.clear();
        }

        //更新缓存判断---重新加载一次
        if (isExistNewCode) {
            iStockBaseInfoService.initStockInfo();
        }

        return insertList;
    }

    @Override
    public List<StockDayPrice> downDayHist(Collection<String> codeList, String startDate, String endDate) {
        if (CollectionUtils.isEmpty(codeList)) {
            return null;
        }

        String strDate180 = DateUtil.getDate(new Date(), -180);
        List<StockDayPrice> insertList = new LinkedList<>();
        for (String code : codeList) {
            int sleepTime = 100;
            StockBaseInfo baseInfo = new StockBaseInfo();
            baseInfo.setCode(code);
            baseInfo.setDayUpdateTime(new Date());
            try {
                //2.发起请求某个code历史数据
                List<ZhAHist> zhAHists = akService.stock_zh_a_hist(code, null, startDate, endDate, null);

                //非空的时候才更新
                if (!CollectionUtils.isEmpty(zhAHists)) {
                    baseInfo.setDayTotalRecord(zhAHists.size());
                }

                //数据预处理
                Map<String, Double> closePriceMap = new HashMap<>();
                Set<String> date180Set = new HashSet();
                zhAHists.stream().forEach(e -> {
                    //赛选最近6个月的日期-----6个月历史数据肯定是正确的,不需要全部检测,这个检测意义就不大
                    if (e.getDate().compareTo(strDate180) > 0) {
                        date180Set.add(e.getDate());
                    }

                    closePriceMap.put(e.getDate(), e.getClose());
                });

                //3.处理近6个月的的SkipCodes,超过的数据不要
                if (!CollectionUtils.isEmpty(date180Set)) {
                    List<String> emptyDataList = iTradeDateService.listNotInTradeDate(date180Set);
                    List<String> waitDataList = iTradeDateService.filterSkipCodesByCode(code, emptyDataList);
                    if (!CollectionUtils.isEmpty(waitDataList)) {
                        ThreadUtil.execute(() -> tradeDateMapper.appendCodeByDate(code, waitDataList));//异步处理
                    }
                }

                for (ZhAHist item : zhAHists) {
                    StockDayPrice record = new StockDayPrice();
                    record.setAmount(item.getPrice());
                    record.setHigh(item.getHigh());
                    record.setDate(item.getDate());
                    record.setLow(item.getLow());
                    record.setCode(item.getCode());
                    record.setOpen(item.getOpen());
                    record.setChangePrice(item.getRiseOrFallPrice());
                    record.setPriceRate(item.getRiseOrFallRate());
                    record.setVolume(item.getVolume());
                    record.setClose(item.getClose());

                    //价格均值计算
                    OpenCloseLowHigh cp = CalcUtil.calcPrice(item.getOpen(), item.getClose(), item.getLow(), item.getHigh());
                    record.setHlO(cp.getHighSlowDopen());
                    record.setCoO(cp.getCloseSopenDopen());
                    record.setHl(cp.getHighSlow());
                    record.setCo(cp.getCloseSopen());
                    record.setClHl(cp.getCloseSlowDhighSlow());
                    record.setOlHl(cp.getOpenSlowDhighSlow());
                    record.setHcHl(cp.getHighScloseDhighSlow());
                    record.setHoHl(cp.getHighSopenDhighSlow());
                    record.setAvgHl(cp.getAvghighSlow());
                    record.setAvgOc(cp.getAvgOpenclose());

                    String yesterday = iTradeDateService.getYesterday(item.getDate());//求最近的日期
                    record.setYclose(closePriceMap.get(yesterday));//上一个交易日

                    StockBaseInfo stockBaseInfo = iStockBaseInfoService.getStockCodeMap(code);
                    record.setName(stockBaseInfo == null ? null : stockBaseInfo.getName());
                    insertList.add(record);

                    //2.数据入库
                    if (insertList.size() >= 500) {
                        int counti = stockDayPriceMapper.insertbatch(insertList);
                        insertList.clear();
                    }
                }

                if (insertList.size() > 0) {
                    int counti = stockDayPriceMapper.insertbatch(insertList);
                    insertList.clear();
                }

                //3.当前code数据去重---这个删除很慢
                int countd = stockDayPriceMapper.deleteRepeatByCode(code);

                baseInfo.setDayPullCount(1);
                errorCount.set(0);
            } catch (Exception e) {
                //计算DayPullStatus
                Integer tmpDayPullStatus = stockBaseInfoMapper.getDayPullCountByCode(code);
                if (tmpDayPullStatus == null) {
                    tmpDayPullStatus = -1;
                } else {
                    //之前是1,说明数据完整
                    if (tmpDayPullStatus == 1) {
                        tmpDayPullStatus = 2;//第二次拉取失败了
                    } else {
                        tmpDayPullStatus = (tmpDayPullStatus - 1);
                    }
                }
                baseInfo.setDayPullCount(tmpDayPullStatus);

                sleepTime = 3000;
                errorCount.addAndGet(1);//失败就加1
            }

            //更新表记录
            stockBaseInfoMapper.updateByCode(baseInfo);

            //安全跳出----一直拉取数据会被警用ip
            if (errorCount.get() >= 3) {
                log.error("程序即将中断,连续调用多次异常,次数={}", errorCount.get());
                break;
            }

            //调用延迟---避开ip检测与屏蔽
            try {
                Thread.sleep(sleepTime);//调用异常就延迟的长一点
            } catch (Exception e) {
                //延迟不打印日志
            }
        }

        return insertList;
    }

    @Override
    public Page pageList(StockDayPricePageReq req) {
        PageHelper.startPage(req.getPageNum(), req.getPageSize());
        QueryWrapper<StockDayPrice> queryWrapper = new QueryWrapper();
        if (StringUtils.isNotBlank(req.getCode())) {
            queryWrapper.like("code", req.getCode());
        }
        if (StringUtils.isNotBlank(req.getCodes())) {
            List<String> list = Arrays.asList(req.getCodes().split(","));
            Set<String> tmpSet = list.stream().filter(e -> StringUtils.isNotBlank(e)).collect(Collectors.toSet());
            if (!CollectionUtils.isEmpty(tmpSet)) {
                queryWrapper.in("code", tmpSet);
            }
        }
        if (StringUtils.isNotBlank(req.getStartDate())) {
            queryWrapper.gt("date", req.getStartDate());
        }
        if (StringUtils.isNotBlank(req.getEndDate())) {
            //queryWrapper.lt（）——小于
            //queryWrapper.le（）——小于等于
            queryWrapper.lt("date", req.getEndDate());
        }
        queryWrapper.orderByDesc("id");
        List<StockDayPrice> list = stockDayPriceMapper.selectList(queryWrapper);
        return new Page<>(list, new PageInfo<>(list).getTotal(), req);
    }

    //检查指定区间内的,符合条件都直接拉取,或者重置状态
    @Override
    public Map<String, List<String>> checkDayHis(String startDate, String endDate) {
        Map<String, List<String>> reMap = new HashMap<>();
        List<TradeDate> dateList = iTradeDateService.listDateNotContainStartAndEnd(startDate, endDate);
        int dateSize = dateList.size();

        //求出,指定范围内code中包含的日期
        List<DayPriceVo> valueList = stockDayPriceMapper.groupCodeByDate(startDate, endDate);

        List<String> dayPullStatusList = StockStatusEnum.getDayPullStatus();
        for (DayPriceVo item : valueList) {
            String code = item.getCode();

            StockBaseInfo baseInfo = iStockBaseInfoService.getStockCodeMap(code);
            if (baseInfo == null || !dayPullStatusList.contains(baseInfo.getStatus())) {
                continue; // 异常数据的,也不要
            }

            if (item.getCount() == dateSize) { //数据完整
                continue;
            }

            if (item.getCount() <= 1) { //历史数据只有一条,也没办法处理
                continue;
            }

            //日期比对
            List<String> tmp = new LinkedList<>();
            List<String> dbList = Arrays.asList(item.getDates().split(","));
            Collections.sort(dbList);//排序
            String startTime = dbList.get(0);
            for (TradeDate record : dateList) {
                String strDate = record.getDate();
                String skipCodes = record.getSkipCodes();
                if (dbList.contains(strDate)) {
                    continue;//包含则跳过
                }

                if (startTime.compareTo(strDate) > 0) {
                    //也要跳过
                } else {
                    if (skipCodes != null && skipCodes.contains(code)) {
                        //无数据code,直接跳过
                    } else {
                        tmp.add(strDate);
                    }
                }
            }

            int tmpSize = tmp.size();
            if (tmpSize > 0) {
                reMap.put(code, tmp);

                //根据数据量,来决定下载策略
                if (tmpSize > 5) {
                    StockBaseInfo record = new StockBaseInfo();
                    record.setCode(code);
                    record.setDayPullCount(0);
                    int i = stockBaseInfoMapper.updateByCode(record);
                } else {
                    //下载对应范围内的
                    List<String> list = new LinkedList<>();
                    list.add(code);
                    downDayHist(list, startDate, endDate);
                }
            }
        }
        return reMap;
    }

    @Override
    public List<StockDayPrice> listByCode(String code) {
        QueryWrapper<StockDayPrice> queryWrapper = new QueryWrapper();
        queryWrapper.eq("code", code);
        queryWrapper.orderByDesc("id");
        queryWrapper.last("limit 30");
        return stockDayPriceMapper.selectList(queryWrapper);
    }
}
